/*
 * GenerateMobiDialog.java
 *
 *  created: 4.11.2011
 *  charset: UTF-8
 *  license: MIT (X11) (See LICENSE file for full license)
 */
package cz.mp.k3bg.gui;


import cz.mp.k3bg.BookState;
import cz.mp.k3bg.Images;
import cz.mp.k3bg.core.BookFiles;
import cz.mp.k3bg.core.KindlegenRunner;
import cz.mp.k3bg.core.Metadata;
import cz.mp.k3bg.core.NcxGenerator;
import cz.mp.k3bg.core.OpfGenerator;
import cz.mp.k3bg.log.LoggerManager;
import cz.mp.util.GuiUtils;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.IOException;
import java.io.OutputStream;
import java.util.logging.Logger;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JProgressBar;
import javax.swing.JScrollPane;
import javax.swing.JTextPane;
import javax.swing.SwingWorker;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.DefaultStyledDocument;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
import net.miginfocom.swing.MigLayout;
import static cz.mp.k3bg.TextSource.*;
import static cz.mp.k3bg.Application.*;

/**
 * Dialog pro generování knihy ve formátu {@code mobi}.
 * Zde se také generuje {@code OPF} a {@code NCX}.
 * Soubory se generují na pozadí.
 * 
 * @author Martin Pokorný
 * @version 0.1
 * @see KindlegenPanel
 * @see KindlegenRunner
 */
public class GenerateMobiDialog extends GenerateDialog {

    private static final boolean DEBUG = false;
    private static final Logger logger =
            LoggerManager.getLogger(GenerateMobiDialog.class, DEBUG);

    private static GenerateMobiDialog instance = null;

    private JLabel progressLabel = new JLabel(getLocText("gui.kindlegen.generate.progress"));

    private DefaultStyledDocument progressDocument =
            new DefaultStyledDocument();
    private JTextPane progressTextPane =
            new JTextPane(progressDocument);
    private JScrollPane progressScrollTextPane =
            new JScrollPane(progressTextPane);

    private JProgressBar progressBar = new JProgressBar();
    private JButton stopButton = new JButton(
            getLocText("gui.kindlegen.generate.stop"),
            Images.getImage(Images.STOP));
    
    private JButton closeButton = new JButton(
            getLocText("gui.close"),
            Images.getImage(Images.CANCEL));

    // pro styl v   progressTextPane
    private static final SimpleAttributeSet ATTRIB_TYPO_BOLD = 
            new SimpleAttributeSet();
    private static final SimpleAttributeSet ATTRIB_TYPO = 
            new SimpleAttributeSet();
    static {
        StyleConstants.setBold(ATTRIB_TYPO_BOLD, true);
        StyleConstants.setFontFamily(ATTRIB_TYPO_BOLD, "monospaced");
        StyleConstants.setFontFamily(ATTRIB_TYPO, "monospaced");
    }

    private ProgressTextOutputStream progressOutputStream =
            new ProgressTextOutputStream();

    private GenerateTask generateTask;

    // -----

    /** */
    public GenerateMobiDialog() {
        super(MainFrame.getInstance());
        this.bookFiles = null;
        initComponents();
        initLayout();
        initEventHandlers();
        initDialog();
    }

    /**
     *
     * @return
     */
    private static GenerateMobiDialog getInstance() {
        if (instance == null) {
            logger.fine("new");
            instance = new GenerateMobiDialog();
        }
        return instance;
    }

    /**
     *
     */
    public static void showDialog()  {
        getInstance();

        instance.showThisDialog();
    }

    /**
     * 
     */
    private void showThisDialog() {
        logger.info("");
        progressTextPane.setText("");

        generate();

        setVisible(true);
    }

    /**
     *
     */
    private void initDialog() {
        setTitle(getLocText("gui.kindlegen.generate"));

        pack();
        setMinimumSize(new Dimension(400, getHeight() + 50));
        setSize(MainFrame.getInstance().getWidth() - 60, MainFrame.getInstance().getHeight() - 150);

        setLocationRelativeTo(MainFrame.getInstance());
    }

    /**
     *
     */
    private void initComponents() {
        setProcessingState(false);

        progressTextPane.setEditable(false);
        GuiUtils.adjustBackground(progressTextPane);

        progressBar.setIndeterminate(true);

        progressScrollTextPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
        progressScrollTextPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
    }
    
    /**
     * 
     */
    private void initLayout() {
        this.setLayout(new MigLayout("",
                "unrel[fill,grow]unrel",
                "unrel[]rel[fill,grow]para[bottom,nogrid]unrel"));

        this.add(progressLabel, "wrap");
        this.add(progressScrollTextPane, "wrap");

        this.add(progressBar, "hidemode 3, w 100:pref:pref, split");
        this.add(stopButton, "hidemode 3, tag cancel");
        
        this.add(closeButton, "hidemode 3, tag cancel");
        
    }

    /**
     *
     */
    private void initEventHandlers() {
        stopButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                stopGeneration();
            }
        });

        closeButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                closeDialog();
            }
        });

        this.addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent e) {
                closeDialog();
            }
        });
    }

    /**
     * 
     */
    private void stopGeneration() {
        if (BookState.getMainInstance() != null
                && BookState.getMainInstance().getKindlegenRunner() != null) {
            BookState.getMainInstance().getKindlegenRunner().stop();
        }
        if (generateTask != null) {
            generateTask.cancel(false);
        }
        setProcessingState(false);
    }

    /**
     *
     */
    @Override
    protected void closeDialog() {
        stopGeneration();
        setVisible(false);
    }


    @Override
    protected void generate() {
        generateTask = new GenerateTask();
        setProcessingState(true);
        generateTask.execute();
    }

    /**
     *
     * @param processing
     */
    private void setProcessingState(boolean processing) {
        stopButton.setVisible(processing);
        progressBar.setVisible(processing);
        closeButton.setVisible(!processing);
    }


    /**
     *
     * @param text
     */
    private void appendToProgressLog(String text) {
        try {
            AttributeSet attrib = ATTRIB_TYPO;
            if (text.startsWith(">")
                    || text.startsWith("$")
                    || text.startsWith("#")
                    || text.startsWith("/")
                    || text.startsWith("\\")
//                    || text.toLowerCase().contains("v8")
                    || text.toLowerCase().contains("error")
                    || text.toLowerCase().contains("canceled")
                    || text.toLowerCase().contains("successfully")
                    || text.toLowerCase().contains("generated")
                    || text.toLowerCase().contains("exception")) {
                attrib = ATTRIB_TYPO_BOLD;
            }
            
            progressDocument.insertString(
                    progressDocument.getLength(), text, attrib);
            progressTextPane.setCaretPosition(progressDocument.getLength());

        } catch (BadLocationException ex) {
            logger.warning(ex.getMessage());
            if (DEBUG) {  ex.printStackTrace();  }
            Dialogs.showErrorDialog(ex);
        }
    }
    

    // -------------------------------------------------------------------------

    /**
     * {@code OutputStream}, který zapisuje do {@linkplain progressTextPane}.
     *
     * @see #appendToProgressLog(java.lang.String)
     */
    private class ProgressTextOutputStream extends OutputStream {

        public ProgressTextOutputStream() {
            super();
        }

        @Override
        public void write(int b) throws IOException {
//            super.write(b);
            String line = new String(new char[]{ (char)b });
            appendToProgressLog(line);
        }

        @Override
        public void write(byte[] b) throws IOException {
//            super.write(b);
            String line = new String(b);
            appendToProgressLog(line);
        }

        @Override
        public void write(byte[] b, int off, int len) throws IOException {
//            super.write(b, off, len);
            String line = new String(b, off, len);
            appendToProgressLog(line);
        }

        public void write(String line) throws IOException {
            appendToProgressLog(line);
        }
    }

    // -------------------------------------------------------------------------
    
    /**
     * Úloha prováděná na pozadí GUI, která generuje soubory {@code ncx},
     * {@code opf} a soubor knihy {@code mobi}.
     *
     * @see BookState
     * @see NcxGenerator
     * @see OpfGenerator
     * @see KindlegenRunner
     */
    private class GenerateTask extends SwingWorker<Boolean, Integer> {

        private Metadata metadata;
        private BookFiles bookFiles;
        private KindlegenRunner kindlegenRunner;

        /**
         *
         */
        private void init() {
            logger.fine("");

            if (progressOutputStream == null) {
                throw new IllegalStateException("progressOutputStream = null");
            }
            BookState bookState = BookState.getMainInstance();
            if (bookState == null) {
                throw new IllegalStateException("bookState = null");
            }

            metadata = bookState.getMetadata();
            logger.fine("metadata = null ?  " + (metadata == null));
            if (metadata == null) {
                throw new IllegalStateException("metadata = null");
            }

            bookFiles = bookState.getBookFiles();
            logger.fine("bookFiles = null ?  " + (bookFiles == null));
            if (bookFiles == null) {
                throw new IllegalStateException("bookFiles = null");
            }

            kindlegenRunner = bookState.getKindlegenRunner();            
            logger.fine("kindlegen = null ?  " + (kindlegenRunner == null));
            if (kindlegenRunner == null) {
                throw new IllegalStateException("kindlegenRunner = null");
            }
            if (kindlegenRunner.getKindlegen() == null) {
                throw new IllegalStateException("kindlegen = null");
            }
            logger.fine("kindlegen command =  " +
                        kindlegenRunner.getKindlegen().getCommand());
        }
        
        /**
         * 
         * @throws IOException 
         */
        private void generateNcx() throws IOException {
            logger.info("# NCX");
            progressOutputStream.write("# NCX" + EOL);
            bookFiles.setDefaultNcx();
            NcxGenerator ncxg = new NcxGenerator(bookFiles, metadata);
            ncxg.generate();
        }

        /**
         *
         * @throws IOException
         */
        private void generateOpf() throws IOException {
            logger.info("# OPF");
            progressOutputStream.write("# OPF" + EOL);
            bookFiles.setDefaultOpf(metadata);
            OpfGenerator opfg = new OpfGenerator(bookFiles, metadata);
            opfg.generate();
        }

        /**
         *
         * @throws IOException
         */
        private void generateMobi() throws IOException {
            logger.info("# MOBI");
            progressOutputStream.write("# MOBI" + EOL + EOL);

            kindlegenRunner.setKindlegenCommandOutputStream(
                    progressOutputStream);
            kindlegenRunner.setOpfFileNamePath(
                    bookFiles.getOpf().getAbsolutePath());
            kindlegenRunner.setOutputFileName(
                    metadata.createStdBaseFileName()+".mobi");

            kindlegenRunner.run();
        }

        @Override
        protected Boolean doInBackground() throws Exception {
            logger.fine("");

            try {
                init();
            } catch (IllegalStateException ex) {
                logger.warning(ex.getMessage());
                if (DEBUG) {  ex.printStackTrace();  }
                Dialogs.showErrorDialog(ex);

                return Boolean.FALSE;
            }

            try {
                generateBaseStyle(this.bookFiles);
                if(isCancelled()) {
                    return Boolean.FALSE;
                }

                generateNcx();
                if(isCancelled()) {
                    return Boolean.FALSE;
                }
                progressOutputStream.write("OK" + EOL + EOL);

                generateOpf();
                if(isCancelled()) {
                    return Boolean.FALSE;
                }
                progressOutputStream.write("OK" + EOL + EOL);

//                if (kindlegen == null) {
//                    return Boolean.FALSE;
//                }
                generateMobi();
                if(isCancelled()) {
                    return Boolean.FALSE;
                }
                progressOutputStream.write("OK" + EOL);

                return Boolean.TRUE;

            } catch (IOException ex) {
                logger.warning(ex.getMessage());
                if (DEBUG) {  ex.printStackTrace();  }
                
                Dialogs.showErrorDialog(ex);
                try {
                    progressOutputStream.write(EOL + ex + EOL);
                } catch(IOException ex2) {
                    // nic
                }
                
                return Boolean.FALSE;
            }
        }

        @Override
        protected void done() {
            try {
                if (isCancelled()) {
                    logger.info("Canceled!");
                    progressOutputStream.write(EOL + "Canceled!" + EOL);
                }
            } catch (IOException ex) {
                logger.warning(ex.getMessage());
                if (DEBUG) {  ex.printStackTrace();  }
                Dialogs.showErrorDialog(ex);
            }
            setProcessingState(false);
        }
    }

}   // GenerateMobiDialog.java
